#ifndef SIMPLE_SUPPORT_VUPLE_HPP
#define SIMPLE_SUPPORT_VUPLE_HPP

#include <tuple> // std::tuple std::tuple_size std::tuple_element
#include <functional> // std::logical_or std::logical_and
#include <utility> // std::move std::declval std::index_sequence std::forward std::make_index_sequence
#include <iterator> // std::iterator_traits std::begin std::end
#include <type_traits>// std::common_type_t std::conditional_t std::integral_constant std::remove_reference_t
#include <cstddef> // std::size_t std::nullptr_t

#include "tuple_utils/operators.hpp" // operators::adl operator_flag::all define_tuple_operators tuple_operator_implicit_conversion
#include "tuple_utils/reduction.hpp" // tuple_reduction
#include "algorithm/traits.hpp" // iterable equality_comparable
#include "type_traits/is_template_instance.hpp" // is_template_instance_v
#include "meta/pend.hpp" // meta::concat_t
#include "meta/transform.hpp" // meta::transform_t

namespace simple::support
{

	template <typename T>
	using tuple_disjunction = tuple_reduction<bool, std::logical_or<>, T>;
	template <typename T>
	using tuple_conjunction = tuple_reduction<bool, std::logical_and<>, T>;

	template <typename T>
	[[nodiscard]] constexpr
	tuple_disjunction<T> to_disjunction(tuple_conjunction<T> x) noexcept
	{ return x.value; }
	template <typename T>
	[[nodiscard]] constexpr
	tuple_conjunction<T> to_conjunction(tuple_disjunction<T> x) noexcept
	{ return x.value; }

	// [[nodiscard]] constexpr
	// bool to_disjunction(bool x) noexcept
	// { return x; }
	// [[nodiscard]] constexpr
	// bool to_conjunction(bool x) noexcept
	// { return x; }

	// De Morgan's laws
	template <typename T>
	[[nodiscard]] constexpr
	tuple_disjunction<T> operator~(const tuple_conjunction<T>& x) noexcept
	{ return ~x.value; }
	template <typename T>
	[[nodiscard]] constexpr
	tuple_conjunction<T> operator~(const tuple_disjunction<T>& x) noexcept
	{ return ~x.value; }

	template <typename... Ts>
	struct vuple : std::tuple<Ts...>, operators::adl
	{
		using std::tuple<Ts...>::tuple;

		// template <typename Other>
		// constexpr vuple(Other t) : std::tuple<Ts...>(std::move(t)) {}
		constexpr vuple(std::tuple<Ts...> t) : std::tuple<Ts...>(std::move(t)) {}
		template <typename Other>
		constexpr vuple& operator=(Other t) { std::tuple<Ts...>::operator=(std::move(t)); return *this; }
		constexpr vuple& operator=(std::tuple<Ts...> t) { std::tuple<Ts...>::operator=(std::move(t)); return *this; }
	};
	template <typename... Ts> vuple(Ts...) -> vuple<Ts...>;
	template <typename... Ts> vuple(vuple<Ts...>) -> vuple<vuple<Ts...>>; // see musings/vector_of_vector.cpp
	template <typename... Ts>
	constexpr void swap(vuple<Ts...>& x, vuple<Ts...>& y) noexcept(noexcept(x.swap(y)))
	{ x.swap(y); }
	template <typename... Ts>
	constexpr void swap(const vuple<Ts...>& x, const vuple<Ts...>& y) noexcept(noexcept(x.swap(y)))
	{ x.swap(y); }
	template <typename It, typename It2>
	[[nodiscard]] constexpr auto reaches(const It& x, const It2& y)
	noexcept(noexcept(to_disjunction(x == y)))
	requires (iterable<It> || iterable<It2>) && equality_comparable<It,It2> &&
		(is_template_instance_v<vuple, It> || is_template_instance_v<vuple, It2>)
	{
		return to_disjunction(x == y);
	}

	template <typename T, typename = std::nullptr_t>
	struct iterator_traits;

	template <typename... Ts>
	struct iterator_traits<vuple<Ts...>, decltype(
		(std::declval<typename std::iterator_traits<Ts>::difference_type>(),...),
		(std::declval<typename std::iterator_traits<Ts>::value_type>(),...),
		(std::declval<typename std::iterator_traits<Ts>::pointer>(),...),
		(std::declval<typename std::iterator_traits<Ts>::reference>(),...),
		(std::declval<typename std::iterator_traits<Ts>::iterator_category>(),...),
	nullptr)>
	{
		using difference_type = vuple<typename std::iterator_traits<Ts>::difference_type...>;
		using value_type = vuple<typename std::iterator_traits<Ts>::value_type...>;
		using pointer = void;
		using reference = vuple<typename std::iterator_traits<Ts>::reference...>;
		// TODO: output iterator tag should be common with all but input in this context
		// could check deref assign, but whoever designed this interface doesn't care so why should I?
		using iterator_category = std::common_type_t<typename std::iterator_traits<Ts>::iterator_category...>;
	};

	template <typename... Rs> auto vbegin(Rs&&... rs) { using std::begin; return vuple{begin(rs)...}; }
	template <typename... Rs> auto vend(Rs&&... rs) { using std::end; return vuple{end(rs)...}; }

	template <typename T> struct vuple_depth : std::integral_constant<std::size_t, 0> {};
	template <typename F, typename... R> struct vuple_depth<vuple<F,R...>> : std::integral_constant<std::size_t, vuple_depth<F>{} + 1> {};

	struct vuple_operators_compatibility_tag {};

	namespace detail
	{
		// TODO: c++23 tuple has tuple like constructor so that might be useful?
		template <typename TupleLike, std::size_t... I>
		constexpr auto vuple_from_tuple_like_impl( TupleLike&& t, std::index_sequence<I...> )
		{
			return vuple{adl_helper::get_<I>(std::forward<TupleLike>(t))...};
		}
	} // namespace detail
	template <typename TupleLike>
	constexpr auto vuple_from( TupleLike&& t )
	{
		return detail::vuple_from_tuple_like_impl(std::forward<TupleLike>(t),
			std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<TupleLike>>>{});
	}

} // namespace simple::support

template <typename... Ts>
struct std::tuple_size<simple::support::vuple<Ts...>> : std::tuple_size<tuple<Ts...>> {};
template <std::size_t I, typename... Ts>
struct std::tuple_element<I, simple::support::vuple<Ts...>> : std::tuple_element<I,tuple<Ts...>> {};
template <typename... Ts>
struct std::iterator_traits<simple::support::vuple<Ts...>> : simple::support::iterator_traits<simple::support::vuple<Ts...>> {};

namespace simple
{

	// operators boilerplate
	template <typename... Ts>
	struct support::define_tuple_operators<support::vuple<Ts...>> : trivial_tuple_accessor<support::vuple<Ts...>>
	{
		constexpr static auto enabled_operators = operator_flag::all;
		constexpr static auto enabled_right_element_operators = operator_flag::all;
		constexpr static auto enabled_left_element_operators = operator_flag::all;

		template <typename T>
		struct unwrap_conjunction
		{ using type = T; };
		template <typename T>
		struct unwrap_conjunction<support::tuple_conjunction<T>>
		{ using type = T; };
		template <typename T>
		using unwrap_conjunction_t = typename unwrap_conjunction<T>::type;

		template <typename T>
		struct unwrap_disjunction
		{ using type = T; };
		template <typename T>
		struct unwrap_disjunction<support::tuple_disjunction<T>>
		{ using type = T; };
		template <typename T>
		using unwrap_disjunction_t = typename unwrap_disjunction<T>::type;

		template <typename Deduced, operator_flag of, typename, bool>
		using result = std::conditional_t<of && operator_flag::relational,
			std::conditional_t<of && operator_flag::unequal,
				// pretty awkward, have to undo previous wrapping...
				support::tuple_disjunction<meta::concat_t<vuple<>, meta::transform_t<Deduced, unwrap_disjunction_t>>>,
				support::tuple_conjunction<meta::concat_t<vuple<>, meta::transform_t<Deduced, unwrap_conjunction_t>>>
			>,
			meta::concat_t<vuple<>, Deduced>
		>;

		// TODO: vuple{vuple{1,2,3}} == vuple{1} does not satisfy this. and guess it would have been fine if it was an error, but then tuple op == picks it up and it gets confusing
		// so maybe got to ignore one element wraping vuple here
		using compatibility_tag = support::meta::list<support::vuple_operators_compatibility_tag,
			std::index_sequence<sizeof...(Ts), support::vuple_depth<vuple<Ts...>>{}> // some matrix voodoo from simple::geom
		>;
	};

	template <typename T>
	struct support::tuple_operator_implicit_conversion<support::tuple_conjunction<T>>
	{ using type = T; };
	template <typename T>
	struct support::tuple_operator_implicit_conversion<support::tuple_disjunction<T>>
	{ using type = T; };

} // namespace simple

#endif /* end of include guard */
